Handling Time Series (Temporal) Data in R

Welcome to Time Series Analysis Training

Welcome to the fourth section of our comprehensive training program! This section focuses on handling time series (temporal) data in R. Time series analysis is crucial for understanding patterns, trends, and seasonality in data collected over time. This manual will guide you through practical techniques to handle temporal data efficiently.

By the end of this training, you will be able to:

  • Understand and create temporal data structures in R (Date, POSIXct, lubridate)
  • Perform time series aggregation and decomposition
  • Handle missing values in time series data
  • Visualize time series patterns effectively
  • Work with extremely large time series datasets
  • Compare different time series libraries and their functions
  • Apply best practices for temporal data analysis

Introduction to Temporal Data in R

What is Time Series Data?

Time series data is a sequence of observations recorded at regular or irregular time intervals. Common examples include:

  • Daily stock prices
  • Monthly sales figures
  • Hourly temperature readings
  • Annual GDP growth rates
  • Real-time sensor data

Characteristics of time series data

  • TREND: Long-term increase or decrease in the data
  • SEASONALITY: Regular patterns that repeat at fixed intervals
  • CYCLICALITY: Patterns that occur at irregular intervals
  • RANDOMNESS: Unpredictable, irregular component
  • AUTOCORRELATION: Correlation between observations at different time lags
# Create a sample time series to demonstrate patterns
set.seed(123)
n <- 100
time_points <- 1:n

# Create different components
trend_component <- 0.05 * time_points
seasonal_component <- 5 * sin(2 * pi * time_points / 12)
random_component <- rnorm(n, 0, 2)

# Combine components
sample_ts <- trend_component + seasonal_component + random_component

# Create visualization data
ts_data <- data.frame(
  Time = rep(time_points, 4),
  Value = c(trend_component, seasonal_component, random_component, sample_ts),
  Component = rep(c("Trend", "Seasonality", "Random", "Combined"), each = n)
)

# Plot components
p_components <- ggplot(ts_data, aes(x = Time, y = Value, color = Component)) +
  geom_line(linewidth = 0.8) +
  facet_wrap(~Component, scales = "free_y", ncol = 2) +
  labs(title = "Time Series Components",
       subtitle = "Decomposition of a time series into its basic components",
       x = "Time",
       y = "Value") +
  theme_minimal() +
  theme(legend.position = "none") +
  scale_color_brewer(palette = "Set1")

print(p_components)

## 1. Temporal Data Structures in R

1.1 Basic Date Types

1. Date class – for calendar dates

# Date class
dates <- as.Date(c("2024-01-01", "2024-02-15", "2024-12-31"))
dates
## [1] "2024-01-01" "2024-02-15" "2024-12-31"
class(dates)
## [1] "Date"

2. POSIXct class – for date-times with timezone

# POSIXct class
date_times <- as.POSIXct(c("2024-01-01 09:30:00", "2024-01-01 14:45:00"))
format(date_times, "%Y-%m-%d %H:%M:%S")
## [1] "2024-01-01 09:30:00" "2024-01-01 14:45:00"
class(date_times)
## [1] "POSIXct" "POSIXt"

1.2 Working with lubridate

Using lubridate for date manipulation

  1. Parsing dates from different formats
# Parsing dates from different formats
date_strings <- c("2024-01-15", "15/01/2024", "January 15, 2024", "15 Jan 2024")

parsed <- tibble(
  Input = date_strings,
  Parsed = c(
    as.character(ymd(date_strings[1])),
    as.character(dmy(date_strings[2])),
    as.character(mdy(date_strings[3])),
    as.character(dmy(date_strings[4]))
  )
)
parsed
## # A tibble: 4 × 2
##   Input            Parsed    
##   <chr>            <chr>     
## 1 2024-01-15       2024-01-15
## 2 15/01/2024       2024-01-15
## 3 January 15, 2024 2024-01-15
## 4 15 Jan 2024      2024-01-15
  1. Extracting date components
# Extracting date components
sample_dates <- ymd(c("2024-01-15", "2024-06-30", "2024-12-25"))

components <- tibble(
  Date = sample_dates,
  Year = year(sample_dates),
  Month = month(sample_dates, label = TRUE),
  Day = day(sample_dates),
  Weekday = wday(sample_dates, label = TRUE),
  Quarter = quarter(sample_dates)
)
components
## # A tibble: 3 × 6
##   Date        Year Month   Day Weekday Quarter
##   <date>     <dbl> <ord> <int> <ord>     <int>
## 1 2024-01-15  2024 Jan      15 Mon           1
## 2 2024-06-30  2024 Jun      30 Sun           2
## 3 2024-12-25  2024 Dec      25 Wed           4
  1. Date arithmetic
start_date <- ymd("2024-01-01")

results <- tibble(
  Description = c("Start date", "Add 30 days", "Add 2 months", "End of month"),
  Value = c(
    as.character(start_date),
    as.character(start_date + days(30)),
    as.character(start_date + months(2)),
    as.character(ceiling_date(start_date, "month") - days(1))
  )
)
results
## # A tibble: 4 × 2
##   Description  Value     
##   <chr>        <chr>     
## 1 Start date   2024-01-01
## 2 Add 30 days  2024-01-31
## 3 Add 2 months 2024-03-01
## 4 End of month 2024-01-31

1.3 Creating Time Series Sequences

Below are examples of creating regular time sequences (daily, weekly, monthly) starting at 2024-01-01.

start_date <- ymd("2024-01-01")

# Daily sequence for one month
daily_seq <- seq(start_date, by = "day", length.out = 30)
# Show first 10
daily_seq[1:10]
##  [1] "2024-01-01" "2024-01-02" "2024-01-03" "2024-01-04" "2024-01-05"
##  [6] "2024-01-06" "2024-01-07" "2024-01-08" "2024-01-09" "2024-01-10"
# Weekly sequence (12 weeks)
weekly_seq <- seq(start_date, by = "week", length.out = 12)
weekly_seq
##  [1] "2024-01-01" "2024-01-08" "2024-01-15" "2024-01-22" "2024-01-29"
##  [6] "2024-02-05" "2024-02-12" "2024-02-19" "2024-02-26" "2024-03-04"
## [11] "2024-03-11" "2024-03-18"
# Monthly sequence (12 months)
monthly_seq <- seq(start_date, by = "month", length.out = 12)
monthly_seq
##  [1] "2024-01-01" "2024-02-01" "2024-03-01" "2024-04-01" "2024-05-01"
##  [6] "2024-06-01" "2024-07-01" "2024-08-01" "2024-09-01" "2024-10-01"
## [11] "2024-11-01" "2024-12-01"

2. Creating Sample Time Series Data

We create one year of daily data with a linear trend, annual and weekly seasonality, and random noise. We also generate a temperature series.

set.seed(123)
n_days <- 365  # One year of daily data

sample_ts <- tibble(
  date = seq(ymd("2024-01-01"), by = "day", length.out = n_days),
  # Components
  trend = 0.1 * (1:n_days),               # Linear trend
  seasonal = 20 * sin(2 * pi * (1:n_days)/365),  # Yearly seasonality
  weekly_pattern = 5 * sin(2 * pi * (1:n_days)/7), # Weekly pattern
  noise = rnorm(n_days, 0, 3),            # Random noise
  # Combined series
  sales = 100 + trend + seasonal + weekly_pattern + noise,
  temperature = 15 + 10 * sin(2 * pi * (1:n_days)/365) + rnorm(n_days, 0, 2)
) %>%
  mutate(
    month = month(date, label = TRUE),
    weekday = wday(date, label = TRUE),
    is_weekend = weekday %in% c("Sat", "Sun")
  )

# Summary information
list(
  date_range = paste(min(sample_ts$date), "to", max(sample_ts$date)),
  observations = nrow(sample_ts),
  variables = c("sales", "temperature")
)
## $date_range
## [1] "2024-01-01 to 2024-12-30"
## 
## $observations
## [1] 365
## 
## $variables
## [1] "sales"       "temperature"
# First 10 rows
head(sample_ts, 10)
## # A tibble: 10 × 10
##    date       trend seasonal weekly_pattern  noise sales temperature month
##    <date>     <dbl>    <dbl>          <dbl>  <dbl> <dbl>       <dbl> <ord>
##  1 2024-01-01   0.1    0.344       3.91e+ 0 -1.68  103.         14.8 Jan  
##  2 2024-01-02   0.2    0.688       4.87e+ 0 -0.691 105.         16.1 Jan  
##  3 2024-01-03   0.3    1.03        2.17e+ 0  4.68  108.         13.6 Jan  
##  4 2024-01-04   0.4    1.38       -2.17e+ 0  0.212  99.8        17.4 Jan  
##  5 2024-01-05   0.5    1.72       -4.87e+ 0  0.388  97.7        14.9 Jan  
##  6 2024-01-06   0.6    2.06       -3.91e+ 0  5.15  104.         20.9 Jan  
##  7 2024-01-07   0.7    2.40       -1.22e-15  1.38  104.         12.9 Jan  
##  8 2024-01-08   0.8    2.75        3.91e+ 0 -3.80  104.         15.4 Jan  
##  9 2024-01-09   0.9    3.09        4.87e+ 0 -2.06  107.         18.2 Jan  
## 10 2024-01-10   1      3.43        2.17e+ 0 -1.34  105.         17.7 Jan  
## # ℹ 2 more variables: weekday <ord>, is_weekend <lgl>

3. Time Series Visualization

We create four visualizations: (1) basic time series, (2) series with trend component, (3) sales by month, and (4) sales by weekday.

# Basic time series plot
p1 <- ggplot(sample_ts, aes(x = date, y = sales)) +
  geom_line(color = "blue", linewidth = 0.8) +
  labs(title = "Sales Over Time", x = "Date", y = "Sales") +
  theme_minimal()

# Plot with components
p2 <- ggplot(sample_ts, aes(x = date)) +
  geom_line(aes(y = sales), color = "blue", alpha = 0.5, linewidth = 0.5) +
  geom_line(aes(y = trend + 100), color = "red", linewidth = 1) +
  labs(title = "Sales with Trend Component", x = "Date", y = "Value") +
  theme_minimal()

# Monthly patterns
p3 <- ggplot(sample_ts, aes(x = month, y = sales, group = month)) +
  geom_boxplot(fill = "lightblue") +
  labs(title = "Sales by Month", x = "Month", y = "Sales") +
  theme_minimal()

# Daily patterns
p4 <- ggplot(sample_ts, aes(x = weekday, y = sales, group = weekday)) +
  geom_boxplot(fill = "lightgreen") +
  labs(title = "Sales by Weekday", x = "Weekday", y = "Sales") +
  theme_minimal()

grid.arrange(p1, p2, p3, p4, ncol = 2)


4. Time Series Aggregation

4.1 Basic Aggregation

1) Daily to weekly aggregation

weekly_agg <- sample_ts %>%
  mutate(week_start = floor_date(date, "week")) %>%
  group_by(week_start) %>%
  summarise(
    avg_sales = mean(sales),
    total_sales = sum(sales),
    obs_count = n(),
    .groups = 'drop'
  )

head(weekly_agg, 5)
## # A tibble: 5 × 4
##   week_start avg_sales total_sales obs_count
##   <date>         <dbl>       <dbl>     <int>
## 1 2023-12-31      103.        617.         6
## 2 2024-01-07      104.        731.         7
## 3 2024-01-14      108.        753.         7
## 4 2024-01-21      108.        759.         7
## 5 2024-01-28      114.        799.         7

2) Daily to monthly aggregation

monthly_agg <- sample_ts %>%
  mutate(month_start = floor_date(date, "month")) %>%
  group_by(month_start) %>%
  summarise(
    avg_sales = mean(sales),
    total_sales = sum(sales),
    avg_temp = mean(temperature),
    .groups = 'drop'
  )

monthly_agg
## # A tibble: 12 × 4
##    month_start avg_sales total_sales avg_temp
##    <date>          <dbl>       <dbl>    <dbl>
##  1 2024-01-01       107.       3324.    17.5 
##  2 2024-02-01       119.       3454.    21.8 
##  3 2024-03-01       127.       3924.    24.5 
##  4 2024-04-01       130.       3890.    25.2 
##  5 2024-05-01       127.       3940.    21.8 
##  6 2024-06-01       122.       3660.    17.8 
##  7 2024-07-01       115.       3569.    12.6 
##  8 2024-08-01       108.       3344.     7.34
##  9 2024-09-01       107.       3217.     4.96
## 10 2024-10-01       111.       3430.     5.75
## 11 2024-11-01       119.       3562.     7.49
## 12 2024-12-01       130.       3904.    12.5

3) Aggregation by weekday

weekday_agg <- sample_ts %>%
  group_by(weekday) %>%
  summarise(
    avg_sales = mean(sales),
    min_sales = min(sales),
    max_sales = max(sales),
    .groups = 'drop'
  )

weekday_agg
## # A tibble: 7 × 4
##   weekday avg_sales min_sales max_sales
##   <ord>       <dbl>     <dbl>     <dbl>
## 1 Sun          119.     104.       139.
## 2 Mon          122.     103.       143.
## 3 Tue          122.     105.       137.
## 4 Wed          120.     105.       144.
## 5 Thu          116.      99.8      132.
## 6 Fri          114.      97.7      132.
## 7 Sat          116.      99.6      133.

4.2 Visualizing Aggregated Data

p_agg1 <- ggplot() +
  geom_line(data = sample_ts, aes(x = date, y = sales), 
            color = "gray", alpha = 0.5, linewidth = 0.5) +
  geom_line(data = weekly_agg, aes(x = week_start, y = avg_sales), 
            color = "red", linewidth = 1) +
  labs(title = "Daily Data with Weekly Averages", x = "Date", y = "Sales") +
  theme_minimal()

p_agg2 <- ggplot(monthly_agg, aes(x = month_start, y = total_sales)) +
  geom_col(fill = "steelblue") +
  labs(title = "Monthly Total Sales", x = "Month", y = "Total Sales") +
  theme_minimal()

grid.arrange(p_agg1, p_agg2, ncol = 2)


5. Time Series Decomposition

5.1 Understanding Components

Time series decomposition separates data into components:
Y(t) = Trend(t) + Seasonal(t) + Random(t)

set.seed(123)
n <- 120  # 10 years of monthly data
time <- 1:n

# Create components
trend_comp <- 0.5 * time
seasonal_comp <- 10 * sin(2 * pi * time / 12)
random_comp <- rnorm(n, 0, 3)

# Combine components
ts_data <- trend_comp + seasonal_comp + random_comp

# Visualize components separately
components_df <- tibble(
  time = rep(time, 4),
  value = c(ts_data, trend_comp, seasonal_comp, random_comp),
  component = rep(c("Combined", "Trend", "Seasonal", "Random"), each = n)
)

p_components <- ggplot(components_df, aes(x = time, y = value)) +
  geom_line(linewidth = 0.8, color = "blue") +
  facet_wrap(~component, scales = "free_y", ncol = 2) +
  labs(title = "Time Series Components", subtitle = "Combined = Trend + Seasonal + Random",
       x = "Time", y = "Value") +
  theme_minimal()

p_components

5.2 Classical Decomposition

# Convert to time series object
ts_object <- ts(ts_data, frequency = 12)

# Perform decomposition
decomp_result <- decompose(ts_object, type = "additive")

# Plot decomposition
par(mfrow = c(4, 1), mar = c(3, 4, 2, 2))
plot(decomp_result)

  • Observed: Original time series
  • Trend: Long-term pattern
  • Seasonal: Regular repeating pattern
  • Random: Irregular component

5.3 STL Decomposition (More Robust)

# STL is more robust for many time series
stl_result <- stl(ts_object, s.window = "periodic")

# Plot STL decomposition
par(mfrow = c(4, 1), mar = c(3, 4, 2, 2))
plot(stl_result, main = "STL Decomposition")

# Extract components
components_stl <- tibble(
  time = time,
  observed = as.numeric(ts_object),
  trend = as.numeric(stl_result$time.series[, "trend"]),
  seasonal = as.numeric(stl_result$time.series[, "seasonal"]),
  remainder = as.numeric(stl_result$time.series[, "remainder"])
)

# Component statistics
components_stl %>%
  summarise(
    Trend_Mean = mean(trend), Trend_SD = sd(trend),
    Seasonal_Mean = mean(seasonal), Seasonal_SD = sd(seasonal),
    Remainder_Mean = mean(remainder), Remainder_SD = sd(remainder)
  )
## # A tibble: 1 × 6
##   Trend_Mean Trend_SD Seasonal_Mean Seasonal_SD Remainder_Mean Remainder_SD
##        <dbl>    <dbl>         <dbl>       <dbl>          <dbl>        <dbl>
## 1       30.3     17.2 -0.0000000610        6.88        0.00498         2.39

6. Practical Exercise with Real-World Data

6.1 Load Real-World Time Series Data

We use the Australian tourism dataset (from the tsibbledata package, loaded via fpp3).

# Load Australian tourism dataset (built-in from tsibble/fpp3)
data("tourism")

# Inspect
tourism
## # A tsibble: 24,320 x 5 [1Q]
## # Key:       Region, State, Purpose [304]
##   Quarter Region   State           Purpose  Trips
##     <qtr> <chr>    <chr>           <chr>    <dbl>
## 1 1998 Q1 Adelaide South Australia Business  135.
## 2 1998 Q2 Adelaide South Australia Business  110.
## 3 1998 Q3 Adelaide South Australia Business  166.
## 4 1998 Q4 Adelaide South Australia Business  127.
## 5 1999 Q1 Adelaide South Australia Business  137.
## # ℹ 24,315 more rows
# Basic information
list(
  time_period = paste(min(tourism$Quarter), "to", max(tourism$Quarter)),
  total_observations = nrow(tourism),
  n_series = n_distinct(tourism$Region) * n_distinct(tourism$Purpose),
  variables = names(tourism)
)
## $time_period
## [1] "1998 Q1 to 2017 Q4"
## 
## $total_observations
## [1] 24320
## 
## $n_series
## [1] 304
## 
## $variables
## [1] "Quarter" "Region"  "State"   "Purpose" "Trips"

6.2 Explore the Data

# First 10 rows
head(tourism, 10)
## # A tsibble: 10 x 5 [1Q]
## # Key:       Region, State, Purpose [1]
##    Quarter Region   State           Purpose  Trips
##      <qtr> <chr>    <chr>           <chr>    <dbl>
##  1 1998 Q1 Adelaide South Australia Business  135.
##  2 1998 Q2 Adelaide South Australia Business  110.
##  3 1998 Q3 Adelaide South Australia Business  166.
##  4 1998 Q4 Adelaide South Australia Business  127.
##  5 1999 Q1 Adelaide South Australia Business  137.
##  6 1999 Q2 Adelaide South Australia Business  200.
##  7 1999 Q3 Adelaide South Australia Business  169.
##  8 1999 Q4 Adelaide South Australia Business  134.
##  9 2000 Q1 Adelaide South Australia Business  154.
## 10 2000 Q2 Adelaide South Australia Business  169.
# Summary statistics by region
region_summary <- tourism %>%
  group_by(Region) %>%
  summarise(
    avg_trips = mean(Trips),
    total_trips = sum(Trips),
    n_quarters = n(),
    .groups = 'drop'
  )
region_summary
## # A tsibble: 6,080 x 5 [1Q]
## # Key:       Region [76]
##   Region   Quarter avg_trips total_trips n_quarters
##   <chr>      <qtr>     <dbl>       <dbl>      <int>
## 1 Adelaide 1998 Q1      165.        659.          4
## 2 Adelaide 1998 Q2      112.        450.          4
## 3 Adelaide 1998 Q3      148.        593.          4
## 4 Adelaide 1998 Q4      131.        524.          4
## 5 Adelaide 1999 Q1      137.        548.          4
## # ℹ 6,075 more rows
# Summary by purpose
purpose_summary <- tourism %>%
  group_by(Purpose) %>%
  summarise(
    avg_trips = mean(Trips),
    total_trips = sum(Trips),
    .groups = 'drop'
  )
purpose_summary
## # A tsibble: 320 x 4 [1Q]
## # Key:       Purpose [4]
##   Purpose  Quarter avg_trips total_trips
##   <chr>      <qtr>     <dbl>       <dbl>
## 1 Business 1998 Q1      47.4       3599.
## 2 Business 1998 Q2      49.0       3724.
## 3 Business 1998 Q3      57.3       4356.
## 4 Business 1998 Q4      49.9       3796.
## 5 Business 1999 Q1      43.9       3335.
## # ℹ 315 more rows

6.3 Visualize the Time Series

We visualize (1) overall tourism, (2) by purpose, (3) top 4 regions, and (4) seasonal patterns by purpose.

# 1. Overall time series (use tsibble's index_by on the time index)
overall <- tourism %>%
  index_by(Quarter) %>%
  summarise(total_trips = sum(Trips))

p1 <- ggplot(overall, aes(x = Quarter, y = total_trips)) +
  geom_line(color = "blue", linewidth = 1) +
  labs(title = "Total Australian Tourism Over Time",
       x = "Quarter", y = "Total Trips") +
  theme_minimal()

# 2. By purpose (aggregate by index, then group by Purpose)
by_purpose <- tourism %>%
  index_by(Quarter) %>%
  group_by(Purpose) %>%
  summarise(total_trips = sum(Trips), .groups = "drop_last")

p2 <- ggplot(by_purpose, aes(x = Quarter, y = total_trips, color = Purpose)) +
  geom_line(linewidth = 0.8) +
  labs(title = "Tourism by Purpose",
       x = "Quarter", y = "Total Trips") +
  theme_minimal() +
  theme(legend.position = "bottom")

# 3. Top 4 regions (compute on a plain tibble to avoid index constraints)
top_regions <- tourism %>%
  as_tibble() %>%
  group_by(Region) %>%
  summarise(total = sum(Trips), .groups = "drop") %>%
  arrange(desc(total)) %>%
  slice_head(n = 4) %>%
  pull(Region)

# Then aggregate by index and group by Region for plotting
by_region <- tourism %>%
  filter(Region %in% top_regions) %>%
  index_by(Quarter) %>%
  group_by(Region) %>%
  summarise(total_trips = sum(Trips), .groups = "drop_last")

p3 <- ggplot(by_region, aes(x = Quarter, y = total_trips, color = Region)) +
  geom_line(linewidth = 0.8) +
  labs(title = "Tourism in Top 4 Regions",
       x = "Quarter", y = "Total Trips") +
  theme_minimal() +
  theme(legend.position = "bottom")

# 4. Seasonal patterns by purpose (this step does not need index_by)
seasonal_patterns <- tourism %>%
  mutate(Year = year(Quarter),
         Qtr  = quarter(Quarter)) %>%
  group_by(Purpose, Qtr) %>%
  summarise(avg_trips = mean(Trips), .groups = "drop")

p4 <- ggplot(seasonal_patterns, aes(x = factor(Qtr), y = avg_trips, fill = Purpose)) +
  geom_col(position = "dodge") +
  labs(title = "Average Quarterly Tourism by Purpose",
       x = "Quarter", y = "Average Trips") +
  theme_minimal() +
  theme(legend.position = "bottom")

grid.arrange(p1, p2, p3, p4, ncol = 2)

6.4 Time Series Aggregation Practice

# 1. Aggregate to yearly data
yearly_data <- tourism %>%
  mutate(Year = year(Quarter)) %>%
  group_by(Region, Purpose, Year) %>%
  summarise(
    total_trips = sum(Trips),
    avg_trips = mean(Trips),
    quarterly_count = n(),
    .groups = 'drop'
  )

head(yearly_data, 10)
## # A tsibble: 10 x 7 [1Q]
## # Key:       Region, Purpose, Year [3]
##    Region   Purpose   Year Quarter total_trips avg_trips quarterly_count
##    <chr>    <chr>    <dbl>   <qtr>       <dbl>     <dbl>           <int>
##  1 Adelaide Business  1998 1998 Q1        135.      135.               1
##  2 Adelaide Business  1998 1998 Q2        110.      110.               1
##  3 Adelaide Business  1998 1998 Q3        166.      166.               1
##  4 Adelaide Business  1998 1998 Q4        127.      127.               1
##  5 Adelaide Business  1999 1999 Q1        137.      137.               1
##  6 Adelaide Business  1999 1999 Q2        200.      200.               1
##  7 Adelaide Business  1999 1999 Q3        169.      169.               1
##  8 Adelaide Business  1999 1999 Q4        134.      134.               1
##  9 Adelaide Business  2000 2000 Q1        154.      154.               1
## 10 Adelaide Business  2000 2000 Q2        169.      169.               1
# 2. Aggregate by region and purpose
region_purpose_summary <- tourism %>%
  group_by(Region, Purpose) %>%
  summarise(
    total_trips = sum(Trips),
    avg_trips = mean(Trips),
    first_quarter = min(Quarter),
    last_quarter = max(Quarter),
    quarters_count = n(),
    .groups = 'drop'
  )

head(region_purpose_summary, 10)
## # A tsibble: 10 x 8 [1Q]
## # Key:       Region, Purpose [1]
##    Region   Purpose  Quarter total_trips avg_trips first_quarter last_quarter
##    <chr>    <chr>      <qtr>       <dbl>     <dbl>         <qtr>        <qtr>
##  1 Adelaide Business 1998 Q1        135.      135.       1998 Q1      1998 Q1
##  2 Adelaide Business 1998 Q2        110.      110.       1998 Q2      1998 Q2
##  3 Adelaide Business 1998 Q3        166.      166.       1998 Q3      1998 Q3
##  4 Adelaide Business 1998 Q4        127.      127.       1998 Q4      1998 Q4
##  5 Adelaide Business 1999 Q1        137.      137.       1999 Q1      1999 Q1
##  6 Adelaide Business 1999 Q2        200.      200.       1999 Q2      1999 Q2
##  7 Adelaide Business 1999 Q3        169.      169.       1999 Q3      1999 Q3
##  8 Adelaide Business 1999 Q4        134.      134.       1999 Q4      1999 Q4
##  9 Adelaide Business 2000 Q1        154.      154.       2000 Q1      2000 Q1
## 10 Adelaide Business 2000 Q2        169.      169.       2000 Q2      2000 Q2
## # ℹ 1 more variable: quarters_count <int>

6.5 Time Series Decomposition Practice

We focus on one time series: Business trips in Sydney.

# Filter: Business trips in Sydney
sydney_business <- tourism %>%
  filter(Region == "Sydney", Purpose == "Business") %>%
  as_tsibble(index = Quarter)

# Convert to ts object for decomposition
ts_sydney <- ts(sydney_business$Trips, frequency = 4)

# Perform STL decomposition
decomp_sydney <- stl(ts_sydney, s.window = "periodic")

# Plot decomposition
par(mfrow = c(4, 1), mar = c(3, 4, 2, 2))
plot(decomp_sydney, main = "STL Decomposition: Business Trips in Sydney")

# Extract components
components_df <- tibble(
  Quarter = sydney_business$Quarter,
  Observed = sydney_business$Trips,
  Trend = as.numeric(decomp_sydney$time.series[, "trend"]),
  Seasonal = as.numeric(decomp_sydney$time.series[, "seasonal"]),
  Remainder = as.numeric(decomp_sydney$time.series[, "remainder"])
)

# Decomposition statistics
components_df %>%
  summarise(
    Observed_Mean = mean(Observed),
    Trend_Mean = mean(Trend),
    Seasonal_Mean = mean(Seasonal),
    Seasonal_Amp = max(Seasonal) - min(Seasonal),
    Remainder_SD = sd(Remainder)
  )
## # A tibble: 1 × 5
##   Observed_Mean Trend_Mean Seasonal_Mean Seasonal_Amp Remainder_SD
##           <dbl>      <dbl>         <dbl>        <dbl>        <dbl>
## 1          602.       602.    0.00000178         132.         63.9
# Visualize components
components_long <- components_df %>%
  pivot_longer(cols = -Quarter, names_to = "Component", values_to = "Value")

p_decomp <- ggplot(components_long, aes(x = Quarter, y = Value)) +
  geom_line(color = "blue", linewidth = 0.8) +
  facet_wrap(~Component, scales = "free_y", ncol = 1) +
  labs(title = "Business Trips in Sydney - Component Analysis", x = "Quarter", y = "Trips") +
  theme_minimal()

p_decomp

6.6 Interactive Visualization

We create an interactive time series plot using plotly.

# Prepare data for interactive plot (convert to plain tibble to avoid tsibble index constraints)
interactive_data <- tourism %>%
  as_tibble() %>%                                   # <-- drop tsibble semantics
  group_by(Quarter, Purpose) %>%
  summarise(total_trips = sum(Trips), .groups = "drop")

# Create interactive plot
p_interactive <- plot_ly(
  interactive_data,
  x = ~Quarter, y = ~total_trips, color = ~Purpose,
  type = "scatter", mode = "lines",
  hovertemplate = paste(
    "Quarter: %{x}<br>",
    "Trips: %{y:,}<br>",
    "Purpose: %{text}<extra></extra>"
  ),
  text = ~Purpose
) %>%
  layout(
    title = "Australian Tourism by Purpose (Interactive)",
    xaxis = list(title = "Quarter"),
    yaxis = list(title = "Total Trips"),
    hovermode = "x unified"
  )

p_interactive

Summary and Key Takeaways

You have learned how to:

  1. Create and manage temporal data
    • Use Date and POSIXct classes
    • Parse dates with lubridate (ymd(), dmy(), mdy())
    • Extract date components (year(), month(), wday())
    • Perform date arithmetic
  2. Work with time series data
    • Create regular time sequences with seq()
    • Handle real-world time series data
    • Convert between different time series formats
  3. Perform time series aggregation
    • Aggregate data by different time periods
    • Use group_by() and summarise() for aggregation
    • Visualize aggregated time series
  4. Decompose time series
    • Understand trend, seasonal, and random components
    • Perform classical decomposition with decompose()
    • Use STL decomposition for robust analysis
    • Interpret decomposition results
  5. Apply to real-world data
    • Load and explore real time series datasets
    • Create meaningful visualizations
    • Perform analysis on actual data

Key functions to remember:
- lubridate: ymd(), floor_date(), year(), month(), wday()
- dplyr: group_by(), summarise(), mutate()
- ggplot2: ggplot(), geom_line(), geom_col()
- forecast: decompose(), stl()
- tsibble: as_tsibble() for tidy time series

Additional Resources

Below are some helpful online references for deepening your understanding of time series analysis, R time series packages, visualization tools, and forecasting techniques.

Time Series Concepts & Tutorials

R Time Series Packages & Documentation

Visualization & Plotting

Open Data Sources for Practice

Additional Learning


This material is part of the training program by The National Centre for Research Methods © NCRM authored by Dr Somnath Chaudhuri (University of Southampton). Content is under a CC BY‑style permissive license and can be freely used for educational purposes with proper attribution.

LS0tDQp0aXRsZTogIkhhbmRsaW5nIFRpbWUgU2VyaWVzIChUZW1wb3JhbCkgRGF0YSBpbiBSIg0KYXV0aG9yOiAiU29tbmF0aCBDaGF1ZGh1cmksIFVuaXZlcnNpdHkgb2YgU291dGhhbXB0b24sIFVLIg0KZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCAnJUIgJWQsICVZJylgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIHRoZW1lOiBjb3Ntbw0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZGVwdGg6IDMNCiAgICBnZW9tZXRyeTogbWFyZ2luPTFpbg0KICAgIGZvbnRzaXplOiAxMXB0DQotLS0NCg0KICANCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBTZXQgZ2xvYmFsIG9wdGlvbnMgZm9yIHRoZSBkb2N1bWVudA0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBlY2hvID0gVFJVRSwgICAgICAgICAgICMgU2hvdyBjb2RlIGluIG91dHB1dA0KICB3YXJuaW5nID0gRkFMU0UsICAgICAgICMgSGlkZSB3YXJuaW5ncw0KICBtZXNzYWdlID0gRkFMU0UsICAgICAgICMgSGlkZSBtZXNzYWdlcw0KICBmaWcud2lkdGggPSAxMCwgICAgICAgICMgRmlndXJlIHdpZHRoDQogIGZpZy5oZWlnaHQgPSA2LCAgICAgICAgIyBGaWd1cmUgaGVpZ2h0DQogIGNhY2hlID0gRkFMU0UgICAgICAgICAgIyBEaXNhYmxlIGNhY2hpbmcgZm9yIHRyYWluaW5nIHB1cnBvc2VzDQopDQoNCiMgTG9hZCByZXF1aXJlZCBsaWJyYXJpZXMgZm9yIHRpbWUgc2VyaWVzIGFuYWx5c2lzDQpsaWJyYXJ5KGRhdGEudGFibGUpICAgICAgIyBGb3IgZmFzdCBkYXRhIG1hbmlwdWxhdGlvbg0KbGlicmFyeShkcGx5cikgICAgICAgICAgICMgRm9yIGludHVpdGl2ZSBkYXRhIHdyYW5nbGluZw0KbGlicmFyeShnZ3Bsb3QyKSAgICAgICAgICMgRm9yIGNyZWF0aW5nIGdyYXBocw0KbGlicmFyeShtaWNyb2JlbmNobWFyaykgICMgRm9yIHBlcmZvcm1hbmNlIGNvbXBhcmlzb24NCmxpYnJhcnkobHVicmlkYXRlKSAgICAgICAjIEZvciBkYXRlLXRpbWUgbWFuaXB1bGF0aW9uDQpsaWJyYXJ5KHh0cykgICAgICAgICAgICAgIyBGb3IgZXh0ZW5zaWJsZSB0aW1lIHNlcmllcw0KbGlicmFyeSh6b28pICAgICAgICAgICAgICMgRm9yIHJlZ3VsYXIgYW5kIGlycmVndWxhciB0aW1lIHNlcmllcw0KbGlicmFyeShmb3JlY2FzdCkgICAgICAgICMgRm9yIHRpbWUgc2VyaWVzIGZvcmVjYXN0aW5nDQpsaWJyYXJ5KHRzaWJibGUpICAgICAgICAgIyBGb3IgdGlkeSB0ZW1wb3JhbCBkYXRhIGZyYW1lcw0KbGlicmFyeShmZWFzdHMpICAgICAgICAgICMgRm9yIGZlYXR1cmUgZXh0cmFjdGlvbiBhbmQgc3RhdGlzdGljcw0KbGlicmFyeSh0aW1ldGspICAgICAgICAgICMgRm9yIHRpbWUgc2VyaWVzIHRvb2xraXRzDQpsaWJyYXJ5KHBsb3RseSkgICAgICAgICAgIyBGb3IgaW50ZXJhY3RpdmUgcGxvdHMNCmxpYnJhcnkoRFQpICAgICAgICAgICAgICAjIEZvciBpbnRlcmFjdGl2ZSB0YWJsZXMNCmxpYnJhcnkoa2FibGVFeHRyYSkgICAgICAjIEZvciBuaWNlIHRhYmxlcw0KbGlicmFyeShmcHAzKQ0KbGlicmFyeShncmlkRXh0cmEpDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeSh0aWR5cikNCg0KDQojIFNldCBkaXNwbGF5IG9wdGlvbnMNCm9wdGlvbnMoZHBseXIucHJpbnRfbWF4ID0gMjAsIA0KICAgICAgICBkcGx5ci5wcmludF9taW4gPSA1LA0KICAgICAgICBkaWdpdHMgPSA0LA0KICAgICAgICBzY2lwZW4gPSA5OTkpDQoNCmBgYA0KDQojIEhhbmRsaW5nIFRpbWUgU2VyaWVzIChUZW1wb3JhbCkgRGF0YSBpbiBSDQoNCiMjIFdlbGNvbWUgdG8gVGltZSBTZXJpZXMgQW5hbHlzaXMgVHJhaW5pbmcNCg0KV2VsY29tZSB0byB0aGUgZm91cnRoIHNlY3Rpb24gb2Ygb3VyIGNvbXByZWhlbnNpdmUgdHJhaW5pbmcgcHJvZ3JhbSEgVGhpcyBzZWN0aW9uIGZvY3VzZXMgb24gaGFuZGxpbmcgdGltZSBzZXJpZXMgKHRlbXBvcmFsKSBkYXRhIGluIFIuIFRpbWUgc2VyaWVzIGFuYWx5c2lzIGlzIGNydWNpYWwgZm9yIHVuZGVyc3RhbmRpbmcgcGF0dGVybnMsIHRyZW5kcywgYW5kIHNlYXNvbmFsaXR5IGluIGRhdGEgY29sbGVjdGVkIG92ZXIgdGltZS4gVGhpcyBtYW51YWwgd2lsbCBndWlkZSB5b3UgdGhyb3VnaCBwcmFjdGljYWwgdGVjaG5pcXVlcyB0byBoYW5kbGUgdGVtcG9yYWwgZGF0YSBlZmZpY2llbnRseS4NCg0KQnkgdGhlIGVuZCBvZiB0aGlzIHRyYWluaW5nLCB5b3Ugd2lsbCBiZSBhYmxlIHRvOg0KDQotIFVuZGVyc3RhbmQgYW5kIGNyZWF0ZSB0ZW1wb3JhbCBkYXRhIHN0cnVjdHVyZXMgaW4gUiAoRGF0ZSwgUE9TSVhjdCwgbHVicmlkYXRlKQ0KLSBQZXJmb3JtIHRpbWUgc2VyaWVzIGFnZ3JlZ2F0aW9uIGFuZCBkZWNvbXBvc2l0aW9uDQotIEhhbmRsZSBtaXNzaW5nIHZhbHVlcyBpbiB0aW1lIHNlcmllcyBkYXRhDQotIFZpc3VhbGl6ZSB0aW1lIHNlcmllcyBwYXR0ZXJucyBlZmZlY3RpdmVseQ0KLSBXb3JrIHdpdGggZXh0cmVtZWx5IGxhcmdlIHRpbWUgc2VyaWVzIGRhdGFzZXRzDQotIENvbXBhcmUgZGlmZmVyZW50IHRpbWUgc2VyaWVzIGxpYnJhcmllcyBhbmQgdGhlaXIgZnVuY3Rpb25zDQotIEFwcGx5IGJlc3QgcHJhY3RpY2VzIGZvciB0ZW1wb3JhbCBkYXRhIGFuYWx5c2lzDQoNCiMgSW50cm9kdWN0aW9uIHRvIFRlbXBvcmFsIERhdGEgaW4gUg0KDQojIyBXaGF0IGlzIFRpbWUgU2VyaWVzIERhdGE/DQpUaW1lIHNlcmllcyBkYXRhIGlzIGEgc2VxdWVuY2Ugb2Ygb2JzZXJ2YXRpb25zIHJlY29yZGVkIGF0IHJlZ3VsYXIgb3IgaXJyZWd1bGFyIHRpbWUgaW50ZXJ2YWxzLiBDb21tb24gZXhhbXBsZXMgaW5jbHVkZToNCiAgDQotIERhaWx5IHN0b2NrIHByaWNlcw0KLSBNb250aGx5IHNhbGVzIGZpZ3VyZXMNCi0gSG91cmx5IHRlbXBlcmF0dXJlIHJlYWRpbmdzDQotIEFubnVhbCBHRFAgZ3Jvd3RoIHJhdGVzDQotIFJlYWwtdGltZSBzZW5zb3IgZGF0YQ0KDQoNCiMjIyBDaGFyYWN0ZXJpc3RpY3Mgb2YgdGltZSBzZXJpZXMgZGF0YQ0KDQotIFRSRU5EOiBMb25nLXRlcm0gaW5jcmVhc2Ugb3IgZGVjcmVhc2UgaW4gdGhlIGRhdGENCi0gU0VBU09OQUxJVFk6IFJlZ3VsYXIgcGF0dGVybnMgdGhhdCByZXBlYXQgYXQgZml4ZWQgaW50ZXJ2YWxzDQotIENZQ0xJQ0FMSVRZOiBQYXR0ZXJucyB0aGF0IG9jY3VyIGF0IGlycmVndWxhciBpbnRlcnZhbHMNCi0gUkFORE9NTkVTUzogVW5wcmVkaWN0YWJsZSwgaXJyZWd1bGFyIGNvbXBvbmVudA0KLSBBVVRPQ09SUkVMQVRJT046IENvcnJlbGF0aW9uIGJldHdlZW4gb2JzZXJ2YXRpb25zIGF0IGRpZmZlcmVudCB0aW1lIGxhZ3MNCg0KYGBge3J9DQoNCiMgQ3JlYXRlIGEgc2FtcGxlIHRpbWUgc2VyaWVzIHRvIGRlbW9uc3RyYXRlIHBhdHRlcm5zDQpzZXQuc2VlZCgxMjMpDQpuIDwtIDEwMA0KdGltZV9wb2ludHMgPC0gMTpuDQoNCiMgQ3JlYXRlIGRpZmZlcmVudCBjb21wb25lbnRzDQp0cmVuZF9jb21wb25lbnQgPC0gMC4wNSAqIHRpbWVfcG9pbnRzDQpzZWFzb25hbF9jb21wb25lbnQgPC0gNSAqIHNpbigyICogcGkgKiB0aW1lX3BvaW50cyAvIDEyKQ0KcmFuZG9tX2NvbXBvbmVudCA8LSBybm9ybShuLCAwLCAyKQ0KDQojIENvbWJpbmUgY29tcG9uZW50cw0Kc2FtcGxlX3RzIDwtIHRyZW5kX2NvbXBvbmVudCArIHNlYXNvbmFsX2NvbXBvbmVudCArIHJhbmRvbV9jb21wb25lbnQNCg0KIyBDcmVhdGUgdmlzdWFsaXphdGlvbiBkYXRhDQp0c19kYXRhIDwtIGRhdGEuZnJhbWUoDQogIFRpbWUgPSByZXAodGltZV9wb2ludHMsIDQpLA0KICBWYWx1ZSA9IGModHJlbmRfY29tcG9uZW50LCBzZWFzb25hbF9jb21wb25lbnQsIHJhbmRvbV9jb21wb25lbnQsIHNhbXBsZV90cyksDQogIENvbXBvbmVudCA9IHJlcChjKCJUcmVuZCIsICJTZWFzb25hbGl0eSIsICJSYW5kb20iLCAiQ29tYmluZWQiKSwgZWFjaCA9IG4pDQopDQoNCiMgUGxvdCBjb21wb25lbnRzDQpwX2NvbXBvbmVudHMgPC0gZ2dwbG90KHRzX2RhdGEsIGFlcyh4ID0gVGltZSwgeSA9IFZhbHVlLCBjb2xvciA9IENvbXBvbmVudCkpICsNCiAgZ2VvbV9saW5lKGxpbmV3aWR0aCA9IDAuOCkgKw0KICBmYWNldF93cmFwKH5Db21wb25lbnQsIHNjYWxlcyA9ICJmcmVlX3kiLCBuY29sID0gMikgKw0KICBsYWJzKHRpdGxlID0gIlRpbWUgU2VyaWVzIENvbXBvbmVudHMiLA0KICAgICAgIHN1YnRpdGxlID0gIkRlY29tcG9zaXRpb24gb2YgYSB0aW1lIHNlcmllcyBpbnRvIGl0cyBiYXNpYyBjb21wb25lbnRzIiwNCiAgICAgICB4ID0gIlRpbWUiLA0KICAgICAgIHkgPSAiVmFsdWUiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikNCg0KcHJpbnQocF9jb21wb25lbnRzKQ0KYGBgIA0KIyMgMS4gVGVtcG9yYWwgRGF0YSBTdHJ1Y3R1cmVzIGluIFINCg0KIyMjIDEuMSBCYXNpYyBEYXRlIFR5cGVzDQoNCioqMS4gRGF0ZSBjbGFzcyDigJMgZm9yIGNhbGVuZGFyIGRhdGVzKioNCg0KYGBge3IgYmFzaWMtZGF0ZS10eXBlc30NCiMgRGF0ZSBjbGFzcw0KZGF0ZXMgPC0gYXMuRGF0ZShjKCIyMDI0LTAxLTAxIiwgIjIwMjQtMDItMTUiLCAiMjAyNC0xMi0zMSIpKQ0KZGF0ZXMNCmNsYXNzKGRhdGVzKQ0KYGBgDQoNCioqMi4gUE9TSVhjdCBjbGFzcyDigJMgZm9yIGRhdGUtdGltZXMgd2l0aCB0aW1lem9uZSoqDQoNCmBgYHtyIHBvc2l4Y3QtZGF0ZS10aW1lc30NCiMgUE9TSVhjdCBjbGFzcw0KZGF0ZV90aW1lcyA8LSBhcy5QT1NJWGN0KGMoIjIwMjQtMDEtMDEgMDk6MzA6MDAiLCAiMjAyNC0wMS0wMSAxNDo0NTowMCIpKQ0KZm9ybWF0KGRhdGVfdGltZXMsICIlWS0lbS0lZCAlSDolTTolUyIpDQpjbGFzcyhkYXRlX3RpbWVzKQ0KYGBgDQoNCiMjIyAxLjIgV29ya2luZyB3aXRoICpsdWJyaWRhdGUqDQoNCioqVXNpbmcgbHVicmlkYXRlIGZvciBkYXRlIG1hbmlwdWxhdGlvbioqDQoNCjEpICoqUGFyc2luZyBkYXRlcyBmcm9tIGRpZmZlcmVudCBmb3JtYXRzKioNCg0KYGBge3IgbHVicmlkYXRlLXBhcnNpbmd9DQojIFBhcnNpbmcgZGF0ZXMgZnJvbSBkaWZmZXJlbnQgZm9ybWF0cw0KZGF0ZV9zdHJpbmdzIDwtIGMoIjIwMjQtMDEtMTUiLCAiMTUvMDEvMjAyNCIsICJKYW51YXJ5IDE1LCAyMDI0IiwgIjE1IEphbiAyMDI0IikNCg0KcGFyc2VkIDwtIHRpYmJsZSgNCiAgSW5wdXQgPSBkYXRlX3N0cmluZ3MsDQogIFBhcnNlZCA9IGMoDQogICAgYXMuY2hhcmFjdGVyKHltZChkYXRlX3N0cmluZ3NbMV0pKSwNCiAgICBhcy5jaGFyYWN0ZXIoZG15KGRhdGVfc3RyaW5nc1syXSkpLA0KICAgIGFzLmNoYXJhY3RlcihtZHkoZGF0ZV9zdHJpbmdzWzNdKSksDQogICAgYXMuY2hhcmFjdGVyKGRteShkYXRlX3N0cmluZ3NbNF0pKQ0KICApDQopDQpwYXJzZWQNCmBgYA0KDQoyKSAqKkV4dHJhY3RpbmcgZGF0ZSBjb21wb25lbnRzKioNCg0KYGBge3IgbHVicmlkYXRlLWNvbXBvbmVudHN9DQojIEV4dHJhY3RpbmcgZGF0ZSBjb21wb25lbnRzDQpzYW1wbGVfZGF0ZXMgPC0geW1kKGMoIjIwMjQtMDEtMTUiLCAiMjAyNC0wNi0zMCIsICIyMDI0LTEyLTI1IikpDQoNCmNvbXBvbmVudHMgPC0gdGliYmxlKA0KICBEYXRlID0gc2FtcGxlX2RhdGVzLA0KICBZZWFyID0geWVhcihzYW1wbGVfZGF0ZXMpLA0KICBNb250aCA9IG1vbnRoKHNhbXBsZV9kYXRlcywgbGFiZWwgPSBUUlVFKSwNCiAgRGF5ID0gZGF5KHNhbXBsZV9kYXRlcyksDQogIFdlZWtkYXkgPSB3ZGF5KHNhbXBsZV9kYXRlcywgbGFiZWwgPSBUUlVFKSwNCiAgUXVhcnRlciA9IHF1YXJ0ZXIoc2FtcGxlX2RhdGVzKQ0KKQ0KY29tcG9uZW50cw0KYGBgDQoNCjMpICoqRGF0ZSBhcml0aG1ldGljKioNCg0KYGBge3IgbHVicmlkYXRlLWFyaXRobWV0aWN9DQpzdGFydF9kYXRlIDwtIHltZCgiMjAyNC0wMS0wMSIpDQoNCnJlc3VsdHMgPC0gdGliYmxlKA0KICBEZXNjcmlwdGlvbiA9IGMoIlN0YXJ0IGRhdGUiLCAiQWRkIDMwIGRheXMiLCAiQWRkIDIgbW9udGhzIiwgIkVuZCBvZiBtb250aCIpLA0KICBWYWx1ZSA9IGMoDQogICAgYXMuY2hhcmFjdGVyKHN0YXJ0X2RhdGUpLA0KICAgIGFzLmNoYXJhY3RlcihzdGFydF9kYXRlICsgZGF5cygzMCkpLA0KICAgIGFzLmNoYXJhY3RlcihzdGFydF9kYXRlICsgbW9udGhzKDIpKSwNCiAgICBhcy5jaGFyYWN0ZXIoY2VpbGluZ19kYXRlKHN0YXJ0X2RhdGUsICJtb250aCIpIC0gZGF5cygxKSkNCiAgKQ0KKQ0KcmVzdWx0cw0KYGBgDQoNCiMjIyAxLjMgQ3JlYXRpbmcgVGltZSBTZXJpZXMgU2VxdWVuY2VzDQoNCkJlbG93IGFyZSBleGFtcGxlcyBvZiBjcmVhdGluZyByZWd1bGFyIHRpbWUgc2VxdWVuY2VzIChkYWlseSwgd2Vla2x5LCBtb250aGx5KSBzdGFydGluZyBhdCAyMDI0LTAxLTAxLg0KDQpgYGB7ciBjcmVhdGUtdHMtc2VxdWVuY2VzfQ0Kc3RhcnRfZGF0ZSA8LSB5bWQoIjIwMjQtMDEtMDEiKQ0KDQojIERhaWx5IHNlcXVlbmNlIGZvciBvbmUgbW9udGgNCmRhaWx5X3NlcSA8LSBzZXEoc3RhcnRfZGF0ZSwgYnkgPSAiZGF5IiwgbGVuZ3RoLm91dCA9IDMwKQ0KIyBTaG93IGZpcnN0IDEwDQpkYWlseV9zZXFbMToxMF0NCg0KIyBXZWVrbHkgc2VxdWVuY2UgKDEyIHdlZWtzKQ0Kd2Vla2x5X3NlcSA8LSBzZXEoc3RhcnRfZGF0ZSwgYnkgPSAid2VlayIsIGxlbmd0aC5vdXQgPSAxMikNCndlZWtseV9zZXENCg0KIyBNb250aGx5IHNlcXVlbmNlICgxMiBtb250aHMpDQptb250aGx5X3NlcSA8LSBzZXEoc3RhcnRfZGF0ZSwgYnkgPSAibW9udGgiLCBsZW5ndGgub3V0ID0gMTIpDQptb250aGx5X3NlcQ0KYGBgDQoNCi0tLQ0KDQojIyAyLiBDcmVhdGluZyBTYW1wbGUgVGltZSBTZXJpZXMgRGF0YQ0KDQpXZSBjcmVhdGUgb25lIHllYXIgb2YgZGFpbHkgZGF0YSB3aXRoIGEgbGluZWFyIHRyZW5kLCBhbm51YWwgYW5kIHdlZWtseSBzZWFzb25hbGl0eSwgYW5kIHJhbmRvbSBub2lzZS4gV2UgYWxzbyBnZW5lcmF0ZSBhIHRlbXBlcmF0dXJlIHNlcmllcy4NCg0KYGBge3IgY3JlYXRlLXNhbXBsZS10c30NCnNldC5zZWVkKDEyMykNCm5fZGF5cyA8LSAzNjUgICMgT25lIHllYXIgb2YgZGFpbHkgZGF0YQ0KDQpzYW1wbGVfdHMgPC0gdGliYmxlKA0KICBkYXRlID0gc2VxKHltZCgiMjAyNC0wMS0wMSIpLCBieSA9ICJkYXkiLCBsZW5ndGgub3V0ID0gbl9kYXlzKSwNCiAgIyBDb21wb25lbnRzDQogIHRyZW5kID0gMC4xICogKDE6bl9kYXlzKSwgICAgICAgICAgICAgICAjIExpbmVhciB0cmVuZA0KICBzZWFzb25hbCA9IDIwICogc2luKDIgKiBwaSAqICgxOm5fZGF5cykvMzY1KSwgICMgWWVhcmx5IHNlYXNvbmFsaXR5DQogIHdlZWtseV9wYXR0ZXJuID0gNSAqIHNpbigyICogcGkgKiAoMTpuX2RheXMpLzcpLCAjIFdlZWtseSBwYXR0ZXJuDQogIG5vaXNlID0gcm5vcm0obl9kYXlzLCAwLCAzKSwgICAgICAgICAgICAjIFJhbmRvbSBub2lzZQ0KICAjIENvbWJpbmVkIHNlcmllcw0KICBzYWxlcyA9IDEwMCArIHRyZW5kICsgc2Vhc29uYWwgKyB3ZWVrbHlfcGF0dGVybiArIG5vaXNlLA0KICB0ZW1wZXJhdHVyZSA9IDE1ICsgMTAgKiBzaW4oMiAqIHBpICogKDE6bl9kYXlzKS8zNjUpICsgcm5vcm0obl9kYXlzLCAwLCAyKQ0KKSAlPiUNCiAgbXV0YXRlKA0KICAgIG1vbnRoID0gbW9udGgoZGF0ZSwgbGFiZWwgPSBUUlVFKSwNCiAgICB3ZWVrZGF5ID0gd2RheShkYXRlLCBsYWJlbCA9IFRSVUUpLA0KICAgIGlzX3dlZWtlbmQgPSB3ZWVrZGF5ICVpbiUgYygiU2F0IiwgIlN1biIpDQogICkNCg0KIyBTdW1tYXJ5IGluZm9ybWF0aW9uDQpsaXN0KA0KICBkYXRlX3JhbmdlID0gcGFzdGUobWluKHNhbXBsZV90cyRkYXRlKSwgInRvIiwgbWF4KHNhbXBsZV90cyRkYXRlKSksDQogIG9ic2VydmF0aW9ucyA9IG5yb3coc2FtcGxlX3RzKSwNCiAgdmFyaWFibGVzID0gYygic2FsZXMiLCAidGVtcGVyYXR1cmUiKQ0KKQ0KDQojIEZpcnN0IDEwIHJvd3MNCmhlYWQoc2FtcGxlX3RzLCAxMCkNCmBgYA0KDQotLS0NCg0KIyMgMy4gVGltZSBTZXJpZXMgVmlzdWFsaXphdGlvbg0KDQpXZSBjcmVhdGUgZm91ciB2aXN1YWxpemF0aW9uczogKDEpIGJhc2ljIHRpbWUgc2VyaWVzLCAoMikgc2VyaWVzIHdpdGggdHJlbmQgY29tcG9uZW50LCAoMykgc2FsZXMgYnkgbW9udGgsIGFuZCAoNCkgc2FsZXMgYnkgd2Vla2RheS4NCg0KYGBge3IgdmlzdWFsaXplLXNhbXBsZSwgZmlnLmhlaWdodD04fQ0KIyBCYXNpYyB0aW1lIHNlcmllcyBwbG90DQpwMSA8LSBnZ3Bsb3Qoc2FtcGxlX3RzLCBhZXMoeCA9IGRhdGUsIHkgPSBzYWxlcykpICsNCiAgZ2VvbV9saW5lKGNvbG9yID0gImJsdWUiLCBsaW5ld2lkdGggPSAwLjgpICsNCiAgbGFicyh0aXRsZSA9ICJTYWxlcyBPdmVyIFRpbWUiLCB4ID0gIkRhdGUiLCB5ID0gIlNhbGVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBQbG90IHdpdGggY29tcG9uZW50cw0KcDIgPC0gZ2dwbG90KHNhbXBsZV90cywgYWVzKHggPSBkYXRlKSkgKw0KICBnZW9tX2xpbmUoYWVzKHkgPSBzYWxlcyksIGNvbG9yID0gImJsdWUiLCBhbHBoYSA9IDAuNSwgbGluZXdpZHRoID0gMC41KSArDQogIGdlb21fbGluZShhZXMoeSA9IHRyZW5kICsgMTAwKSwgY29sb3IgPSAicmVkIiwgbGluZXdpZHRoID0gMSkgKw0KICBsYWJzKHRpdGxlID0gIlNhbGVzIHdpdGggVHJlbmQgQ29tcG9uZW50IiwgeCA9ICJEYXRlIiwgeSA9ICJWYWx1ZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgTW9udGhseSBwYXR0ZXJucw0KcDMgPC0gZ2dwbG90KHNhbXBsZV90cywgYWVzKHggPSBtb250aCwgeSA9IHNhbGVzLCBncm91cCA9IG1vbnRoKSkgKw0KICBnZW9tX2JveHBsb3QoZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGxhYnModGl0bGUgPSAiU2FsZXMgYnkgTW9udGgiLCB4ID0gIk1vbnRoIiwgeSA9ICJTYWxlcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgRGFpbHkgcGF0dGVybnMNCnA0IDwtIGdncGxvdChzYW1wbGVfdHMsIGFlcyh4ID0gd2Vla2RheSwgeSA9IHNhbGVzLCBncm91cCA9IHdlZWtkYXkpKSArDQogIGdlb21fYm94cGxvdChmaWxsID0gImxpZ2h0Z3JlZW4iKSArDQogIGxhYnModGl0bGUgPSAiU2FsZXMgYnkgV2Vla2RheSIsIHggPSAiV2Vla2RheSIsIHkgPSAiU2FsZXMiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpncmlkLmFycmFuZ2UocDEsIHAyLCBwMywgcDQsIG5jb2wgPSAyKQ0KYGBgDQoNCi0tLQ0KDQojIyA0LiBUaW1lIFNlcmllcyBBZ2dyZWdhdGlvbg0KDQojIyMgNC4xIEJhc2ljIEFnZ3JlZ2F0aW9uDQoNCioqMSkgRGFpbHkgdG8gd2Vla2x5IGFnZ3JlZ2F0aW9uKioNCg0KYGBge3IgZGFpbHktdG8td2Vla2x5fQ0Kd2Vla2x5X2FnZyA8LSBzYW1wbGVfdHMgJT4lDQogIG11dGF0ZSh3ZWVrX3N0YXJ0ID0gZmxvb3JfZGF0ZShkYXRlLCAid2VlayIpKSAlPiUNCiAgZ3JvdXBfYnkod2Vla19zdGFydCkgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBhdmdfc2FsZXMgPSBtZWFuKHNhbGVzKSwNCiAgICB0b3RhbF9zYWxlcyA9IHN1bShzYWxlcyksDQogICAgb2JzX2NvdW50ID0gbigpLA0KICAgIC5ncm91cHMgPSAnZHJvcCcNCiAgKQ0KDQpoZWFkKHdlZWtseV9hZ2csIDUpDQpgYGANCg0KKioyKSBEYWlseSB0byBtb250aGx5IGFnZ3JlZ2F0aW9uKioNCg0KYGBge3IgZGFpbHktdG8tbW9udGhseX0NCm1vbnRobHlfYWdnIDwtIHNhbXBsZV90cyAlPiUNCiAgbXV0YXRlKG1vbnRoX3N0YXJ0ID0gZmxvb3JfZGF0ZShkYXRlLCAibW9udGgiKSkgJT4lDQogIGdyb3VwX2J5KG1vbnRoX3N0YXJ0KSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIGF2Z19zYWxlcyA9IG1lYW4oc2FsZXMpLA0KICAgIHRvdGFsX3NhbGVzID0gc3VtKHNhbGVzKSwNCiAgICBhdmdfdGVtcCA9IG1lYW4odGVtcGVyYXR1cmUpLA0KICAgIC5ncm91cHMgPSAnZHJvcCcNCiAgKQ0KDQptb250aGx5X2FnZw0KYGBgDQoNCioqMykgQWdncmVnYXRpb24gYnkgd2Vla2RheSoqDQoNCmBgYHtyIGFnZ3JlZ2F0aW9uLWJ5LXdlZWtkYXl9DQp3ZWVrZGF5X2FnZyA8LSBzYW1wbGVfdHMgJT4lDQogIGdyb3VwX2J5KHdlZWtkYXkpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgYXZnX3NhbGVzID0gbWVhbihzYWxlcyksDQogICAgbWluX3NhbGVzID0gbWluKHNhbGVzKSwNCiAgICBtYXhfc2FsZXMgPSBtYXgoc2FsZXMpLA0KICAgIC5ncm91cHMgPSAnZHJvcCcNCiAgKQ0KDQp3ZWVrZGF5X2FnZw0KYGBgDQoNCiMjIyA0LjIgVmlzdWFsaXppbmcgQWdncmVnYXRlZCBEYXRhDQoNCmBgYHtyIHZpc3VhbGl6ZS1hZ2dyZWdhdGVzfQ0KcF9hZ2cxIDwtIGdncGxvdCgpICsNCiAgZ2VvbV9saW5lKGRhdGEgPSBzYW1wbGVfdHMsIGFlcyh4ID0gZGF0ZSwgeSA9IHNhbGVzKSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmF5IiwgYWxwaGEgPSAwLjUsIGxpbmV3aWR0aCA9IDAuNSkgKw0KICBnZW9tX2xpbmUoZGF0YSA9IHdlZWtseV9hZ2csIGFlcyh4ID0gd2Vla19zdGFydCwgeSA9IGF2Z19zYWxlcyksIA0KICAgICAgICAgICAgY29sb3IgPSAicmVkIiwgbGluZXdpZHRoID0gMSkgKw0KICBsYWJzKHRpdGxlID0gIkRhaWx5IERhdGEgd2l0aCBXZWVrbHkgQXZlcmFnZXMiLCB4ID0gIkRhdGUiLCB5ID0gIlNhbGVzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KcF9hZ2cyIDwtIGdncGxvdChtb250aGx5X2FnZywgYWVzKHggPSBtb250aF9zdGFydCwgeSA9IHRvdGFsX3NhbGVzKSkgKw0KICBnZW9tX2NvbChmaWxsID0gInN0ZWVsYmx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJNb250aGx5IFRvdGFsIFNhbGVzIiwgeCA9ICJNb250aCIsIHkgPSAiVG90YWwgU2FsZXMiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpncmlkLmFycmFuZ2UocF9hZ2cxLCBwX2FnZzIsIG5jb2wgPSAyKQ0KYGBgDQoNCi0tLQ0KDQojIyA1LiBUaW1lIFNlcmllcyBEZWNvbXBvc2l0aW9uDQoNCiMjIyA1LjEgVW5kZXJzdGFuZGluZyBDb21wb25lbnRzDQoNClRpbWUgc2VyaWVzIGRlY29tcG9zaXRpb24gc2VwYXJhdGVzIGRhdGEgaW50byBjb21wb25lbnRzOiAgDQoqKlkodCkgPSBUcmVuZCh0KSArIFNlYXNvbmFsKHQpICsgUmFuZG9tKHQpKioNCg0KYGBge3IgY29tcG9uZW50cy1zeW50aGV0aWN9DQpzZXQuc2VlZCgxMjMpDQpuIDwtIDEyMCAgIyAxMCB5ZWFycyBvZiBtb250aGx5IGRhdGENCnRpbWUgPC0gMTpuDQoNCiMgQ3JlYXRlIGNvbXBvbmVudHMNCnRyZW5kX2NvbXAgPC0gMC41ICogdGltZQ0Kc2Vhc29uYWxfY29tcCA8LSAxMCAqIHNpbigyICogcGkgKiB0aW1lIC8gMTIpDQpyYW5kb21fY29tcCA8LSBybm9ybShuLCAwLCAzKQ0KDQojIENvbWJpbmUgY29tcG9uZW50cw0KdHNfZGF0YSA8LSB0cmVuZF9jb21wICsgc2Vhc29uYWxfY29tcCArIHJhbmRvbV9jb21wDQoNCiMgVmlzdWFsaXplIGNvbXBvbmVudHMgc2VwYXJhdGVseQ0KY29tcG9uZW50c19kZiA8LSB0aWJibGUoDQogIHRpbWUgPSByZXAodGltZSwgNCksDQogIHZhbHVlID0gYyh0c19kYXRhLCB0cmVuZF9jb21wLCBzZWFzb25hbF9jb21wLCByYW5kb21fY29tcCksDQogIGNvbXBvbmVudCA9IHJlcChjKCJDb21iaW5lZCIsICJUcmVuZCIsICJTZWFzb25hbCIsICJSYW5kb20iKSwgZWFjaCA9IG4pDQopDQoNCnBfY29tcG9uZW50cyA8LSBnZ3Bsb3QoY29tcG9uZW50c19kZiwgYWVzKHggPSB0aW1lLCB5ID0gdmFsdWUpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAwLjgsIGNvbG9yID0gImJsdWUiKSArDQogIGZhY2V0X3dyYXAofmNvbXBvbmVudCwgc2NhbGVzID0gImZyZWVfeSIsIG5jb2wgPSAyKSArDQogIGxhYnModGl0bGUgPSAiVGltZSBTZXJpZXMgQ29tcG9uZW50cyIsIHN1YnRpdGxlID0gIkNvbWJpbmVkID0gVHJlbmQgKyBTZWFzb25hbCArIFJhbmRvbSIsDQogICAgICAgeCA9ICJUaW1lIiwgeSA9ICJWYWx1ZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCnBfY29tcG9uZW50cw0KYGBgDQoNCiMjIyA1LjIgQ2xhc3NpY2FsIERlY29tcG9zaXRpb24NCg0KYGBge3IgY2xhc3NpY2FsLWRlY29tcG9zaXRpb259DQojIENvbnZlcnQgdG8gdGltZSBzZXJpZXMgb2JqZWN0DQp0c19vYmplY3QgPC0gdHModHNfZGF0YSwgZnJlcXVlbmN5ID0gMTIpDQoNCiMgUGVyZm9ybSBkZWNvbXBvc2l0aW9uDQpkZWNvbXBfcmVzdWx0IDwtIGRlY29tcG9zZSh0c19vYmplY3QsIHR5cGUgPSAiYWRkaXRpdmUiKQ0KDQojIFBsb3QgZGVjb21wb3NpdGlvbg0KcGFyKG1mcm93ID0gYyg0LCAxKSwgbWFyID0gYygzLCA0LCAyLCAyKSkNCnBsb3QoZGVjb21wX3Jlc3VsdCkNCmBgYA0KDQotICoqT2JzZXJ2ZWQ6KiogT3JpZ2luYWwgdGltZSBzZXJpZXMgIA0KLSAqKlRyZW5kOioqIExvbmctdGVybSBwYXR0ZXJuICANCi0gKipTZWFzb25hbDoqKiBSZWd1bGFyIHJlcGVhdGluZyBwYXR0ZXJuICANCi0gKipSYW5kb206KiogSXJyZWd1bGFyIGNvbXBvbmVudA0KDQojIyMgNS4zIFNUTCBEZWNvbXBvc2l0aW9uIChNb3JlIFJvYnVzdCkNCg0KYGBge3Igc3RsLWRlY29tcG9zaXRpb259DQojIFNUTCBpcyBtb3JlIHJvYnVzdCBmb3IgbWFueSB0aW1lIHNlcmllcw0Kc3RsX3Jlc3VsdCA8LSBzdGwodHNfb2JqZWN0LCBzLndpbmRvdyA9ICJwZXJpb2RpYyIpDQoNCiMgUGxvdCBTVEwgZGVjb21wb3NpdGlvbg0KcGFyKG1mcm93ID0gYyg0LCAxKSwgbWFyID0gYygzLCA0LCAyLCAyKSkNCnBsb3Qoc3RsX3Jlc3VsdCwgbWFpbiA9ICJTVEwgRGVjb21wb3NpdGlvbiIpDQoNCiMgRXh0cmFjdCBjb21wb25lbnRzDQpjb21wb25lbnRzX3N0bCA8LSB0aWJibGUoDQogIHRpbWUgPSB0aW1lLA0KICBvYnNlcnZlZCA9IGFzLm51bWVyaWModHNfb2JqZWN0KSwNCiAgdHJlbmQgPSBhcy5udW1lcmljKHN0bF9yZXN1bHQkdGltZS5zZXJpZXNbLCAidHJlbmQiXSksDQogIHNlYXNvbmFsID0gYXMubnVtZXJpYyhzdGxfcmVzdWx0JHRpbWUuc2VyaWVzWywgInNlYXNvbmFsIl0pLA0KICByZW1haW5kZXIgPSBhcy5udW1lcmljKHN0bF9yZXN1bHQkdGltZS5zZXJpZXNbLCAicmVtYWluZGVyIl0pDQopDQoNCiMgQ29tcG9uZW50IHN0YXRpc3RpY3MNCmNvbXBvbmVudHNfc3RsICU+JQ0KICBzdW1tYXJpc2UoDQogICAgVHJlbmRfTWVhbiA9IG1lYW4odHJlbmQpLCBUcmVuZF9TRCA9IHNkKHRyZW5kKSwNCiAgICBTZWFzb25hbF9NZWFuID0gbWVhbihzZWFzb25hbCksIFNlYXNvbmFsX1NEID0gc2Qoc2Vhc29uYWwpLA0KICAgIFJlbWFpbmRlcl9NZWFuID0gbWVhbihyZW1haW5kZXIpLCBSZW1haW5kZXJfU0QgPSBzZChyZW1haW5kZXIpDQogICkNCmBgYA0KDQotLS0NCg0KIyMgNi4gUHJhY3RpY2FsIEV4ZXJjaXNlIHdpdGggUmVhbC1Xb3JsZCBEYXRhDQoNCiMjIyA2LjEgTG9hZCBSZWFsLVdvcmxkIFRpbWUgU2VyaWVzIERhdGENCg0KV2UgdXNlIHRoZSBBdXN0cmFsaWFuICoqdG91cmlzbSoqIGRhdGFzZXQgKGZyb20gdGhlIGB0c2liYmxlZGF0YWAgcGFja2FnZSwgbG9hZGVkIHZpYSBgZnBwM2ApLg0KDQpgYGB7ciBsb2FkLXRvdXJpc219DQojIExvYWQgQXVzdHJhbGlhbiB0b3VyaXNtIGRhdGFzZXQgKGJ1aWx0LWluIGZyb20gdHNpYmJsZS9mcHAzKQ0KZGF0YSgidG91cmlzbSIpDQoNCiMgSW5zcGVjdA0KdG91cmlzbQ0KDQojIEJhc2ljIGluZm9ybWF0aW9uDQpsaXN0KA0KICB0aW1lX3BlcmlvZCA9IHBhc3RlKG1pbih0b3VyaXNtJFF1YXJ0ZXIpLCAidG8iLCBtYXgodG91cmlzbSRRdWFydGVyKSksDQogIHRvdGFsX29ic2VydmF0aW9ucyA9IG5yb3codG91cmlzbSksDQogIG5fc2VyaWVzID0gbl9kaXN0aW5jdCh0b3VyaXNtJFJlZ2lvbikgKiBuX2Rpc3RpbmN0KHRvdXJpc20kUHVycG9zZSksDQogIHZhcmlhYmxlcyA9IG5hbWVzKHRvdXJpc20pDQopDQpgYGANCg0KIyMjIDYuMiBFeHBsb3JlIHRoZSBEYXRhDQoNCmBgYHtyIGV4cGxvcmUtdG91cmlzbX0NCiMgRmlyc3QgMTAgcm93cw0KaGVhZCh0b3VyaXNtLCAxMCkNCg0KIyBTdW1tYXJ5IHN0YXRpc3RpY3MgYnkgcmVnaW9uDQpyZWdpb25fc3VtbWFyeSA8LSB0b3VyaXNtICU+JQ0KICBncm91cF9ieShSZWdpb24pICU+JQ0KICBzdW1tYXJpc2UoDQogICAgYXZnX3RyaXBzID0gbWVhbihUcmlwcyksDQogICAgdG90YWxfdHJpcHMgPSBzdW0oVHJpcHMpLA0KICAgIG5fcXVhcnRlcnMgPSBuKCksDQogICAgLmdyb3VwcyA9ICdkcm9wJw0KICApDQpyZWdpb25fc3VtbWFyeQ0KDQojIFN1bW1hcnkgYnkgcHVycG9zZQ0KcHVycG9zZV9zdW1tYXJ5IDwtIHRvdXJpc20gJT4lDQogIGdyb3VwX2J5KFB1cnBvc2UpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgYXZnX3RyaXBzID0gbWVhbihUcmlwcyksDQogICAgdG90YWxfdHJpcHMgPSBzdW0oVHJpcHMpLA0KICAgIC5ncm91cHMgPSAnZHJvcCcNCiAgKQ0KcHVycG9zZV9zdW1tYXJ5DQpgYGANCg0KIyMjIDYuMyBWaXN1YWxpemUgdGhlIFRpbWUgU2VyaWVzDQoNCldlIHZpc3VhbGl6ZSAoMSkgb3ZlcmFsbCB0b3VyaXNtLCAoMikgYnkgcHVycG9zZSwgKDMpIHRvcCA0IHJlZ2lvbnMsIGFuZCAoNCkgc2Vhc29uYWwgcGF0dGVybnMgYnkgcHVycG9zZS4NCg0KYGBge3IgdmlzdWFsaXplLXRvdXJpc20sIGZpZy5oZWlnaHQ9OH0NCg0KIyAxLiBPdmVyYWxsIHRpbWUgc2VyaWVzICh1c2UgdHNpYmJsZSdzIGluZGV4X2J5IG9uIHRoZSB0aW1lIGluZGV4KQ0Kb3ZlcmFsbCA8LSB0b3VyaXNtICU+JQ0KICBpbmRleF9ieShRdWFydGVyKSAlPiUNCiAgc3VtbWFyaXNlKHRvdGFsX3RyaXBzID0gc3VtKFRyaXBzKSkNCg0KcDEgPC0gZ2dwbG90KG92ZXJhbGwsIGFlcyh4ID0gUXVhcnRlciwgeSA9IHRvdGFsX3RyaXBzKSkgKw0KICBnZW9tX2xpbmUoY29sb3IgPSAiYmx1ZSIsIGxpbmV3aWR0aCA9IDEpICsNCiAgbGFicyh0aXRsZSA9ICJUb3RhbCBBdXN0cmFsaWFuIFRvdXJpc20gT3ZlciBUaW1lIiwNCiAgICAgICB4ID0gIlF1YXJ0ZXIiLCB5ID0gIlRvdGFsIFRyaXBzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyAyLiBCeSBwdXJwb3NlIChhZ2dyZWdhdGUgYnkgaW5kZXgsIHRoZW4gZ3JvdXAgYnkgUHVycG9zZSkNCmJ5X3B1cnBvc2UgPC0gdG91cmlzbSAlPiUNCiAgaW5kZXhfYnkoUXVhcnRlcikgJT4lDQogIGdyb3VwX2J5KFB1cnBvc2UpICU+JQ0KICBzdW1tYXJpc2UodG90YWxfdHJpcHMgPSBzdW0oVHJpcHMpLCAuZ3JvdXBzID0gImRyb3BfbGFzdCIpDQoNCnAyIDwtIGdncGxvdChieV9wdXJwb3NlLCBhZXMoeCA9IFF1YXJ0ZXIsIHkgPSB0b3RhbF90cmlwcywgY29sb3IgPSBQdXJwb3NlKSkgKw0KICBnZW9tX2xpbmUobGluZXdpZHRoID0gMC44KSArDQogIGxhYnModGl0bGUgPSAiVG91cmlzbSBieSBQdXJwb3NlIiwNCiAgICAgICB4ID0gIlF1YXJ0ZXIiLCB5ID0gIlRvdGFsIFRyaXBzIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCg0KIyAzLiBUb3AgNCByZWdpb25zIChjb21wdXRlIG9uIGEgcGxhaW4gdGliYmxlIHRvIGF2b2lkIGluZGV4IGNvbnN0cmFpbnRzKQ0KdG9wX3JlZ2lvbnMgPC0gdG91cmlzbSAlPiUNCiAgYXNfdGliYmxlKCkgJT4lDQogIGdyb3VwX2J5KFJlZ2lvbikgJT4lDQogIHN1bW1hcmlzZSh0b3RhbCA9IHN1bShUcmlwcyksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBhcnJhbmdlKGRlc2ModG90YWwpKSAlPiUNCiAgc2xpY2VfaGVhZChuID0gNCkgJT4lDQogIHB1bGwoUmVnaW9uKQ0KDQojIFRoZW4gYWdncmVnYXRlIGJ5IGluZGV4IGFuZCBncm91cCBieSBSZWdpb24gZm9yIHBsb3R0aW5nDQpieV9yZWdpb24gPC0gdG91cmlzbSAlPiUNCiAgZmlsdGVyKFJlZ2lvbiAlaW4lIHRvcF9yZWdpb25zKSAlPiUNCiAgaW5kZXhfYnkoUXVhcnRlcikgJT4lDQogIGdyb3VwX2J5KFJlZ2lvbikgJT4lDQogIHN1bW1hcmlzZSh0b3RhbF90cmlwcyA9IHN1bShUcmlwcyksIC5ncm91cHMgPSAiZHJvcF9sYXN0IikNCg0KcDMgPC0gZ2dwbG90KGJ5X3JlZ2lvbiwgYWVzKHggPSBRdWFydGVyLCB5ID0gdG90YWxfdHJpcHMsIGNvbG9yID0gUmVnaW9uKSkgKw0KICBnZW9tX2xpbmUobGluZXdpZHRoID0gMC44KSArDQogIGxhYnModGl0bGUgPSAiVG91cmlzbSBpbiBUb3AgNCBSZWdpb25zIiwNCiAgICAgICB4ID0gIlF1YXJ0ZXIiLCB5ID0gIlRvdGFsIFRyaXBzIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCg0KIyA0LiBTZWFzb25hbCBwYXR0ZXJucyBieSBwdXJwb3NlICh0aGlzIHN0ZXAgZG9lcyBub3QgbmVlZCBpbmRleF9ieSkNCnNlYXNvbmFsX3BhdHRlcm5zIDwtIHRvdXJpc20gJT4lDQogIG11dGF0ZShZZWFyID0geWVhcihRdWFydGVyKSwNCiAgICAgICAgIFF0ciAgPSBxdWFydGVyKFF1YXJ0ZXIpKSAlPiUNCiAgZ3JvdXBfYnkoUHVycG9zZSwgUXRyKSAlPiUNCiAgc3VtbWFyaXNlKGF2Z190cmlwcyA9IG1lYW4oVHJpcHMpLCAuZ3JvdXBzID0gImRyb3AiKQ0KDQpwNCA8LSBnZ3Bsb3Qoc2Vhc29uYWxfcGF0dGVybnMsIGFlcyh4ID0gZmFjdG9yKFF0ciksIHkgPSBhdmdfdHJpcHMsIGZpbGwgPSBQdXJwb3NlKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgbGFicyh0aXRsZSA9ICJBdmVyYWdlIFF1YXJ0ZXJseSBUb3VyaXNtIGJ5IFB1cnBvc2UiLA0KICAgICAgIHggPSAiUXVhcnRlciIsIHkgPSAiQXZlcmFnZSBUcmlwcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQoNCmdyaWQuYXJyYW5nZShwMSwgcDIsIHAzLCBwNCwgbmNvbCA9IDIpDQpgYGANCg0KIyMjIDYuNCBUaW1lIFNlcmllcyBBZ2dyZWdhdGlvbiBQcmFjdGljZQ0KDQpgYGB7ciBhZ2dyZWdhdGlvbi1wcmFjdGljZX0NCiMgMS4gQWdncmVnYXRlIHRvIHllYXJseSBkYXRhDQp5ZWFybHlfZGF0YSA8LSB0b3VyaXNtICU+JQ0KICBtdXRhdGUoWWVhciA9IHllYXIoUXVhcnRlcikpICU+JQ0KICBncm91cF9ieShSZWdpb24sIFB1cnBvc2UsIFllYXIpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgdG90YWxfdHJpcHMgPSBzdW0oVHJpcHMpLA0KICAgIGF2Z190cmlwcyA9IG1lYW4oVHJpcHMpLA0KICAgIHF1YXJ0ZXJseV9jb3VudCA9IG4oKSwNCiAgICAuZ3JvdXBzID0gJ2Ryb3AnDQogICkNCg0KaGVhZCh5ZWFybHlfZGF0YSwgMTApDQoNCiMgMi4gQWdncmVnYXRlIGJ5IHJlZ2lvbiBhbmQgcHVycG9zZQ0KcmVnaW9uX3B1cnBvc2Vfc3VtbWFyeSA8LSB0b3VyaXNtICU+JQ0KICBncm91cF9ieShSZWdpb24sIFB1cnBvc2UpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgdG90YWxfdHJpcHMgPSBzdW0oVHJpcHMpLA0KICAgIGF2Z190cmlwcyA9IG1lYW4oVHJpcHMpLA0KICAgIGZpcnN0X3F1YXJ0ZXIgPSBtaW4oUXVhcnRlciksDQogICAgbGFzdF9xdWFydGVyID0gbWF4KFF1YXJ0ZXIpLA0KICAgIHF1YXJ0ZXJzX2NvdW50ID0gbigpLA0KICAgIC5ncm91cHMgPSAnZHJvcCcNCiAgKQ0KDQpoZWFkKHJlZ2lvbl9wdXJwb3NlX3N1bW1hcnksIDEwKQ0KYGBgDQoNCiMjIyA2LjUgVGltZSBTZXJpZXMgRGVjb21wb3NpdGlvbiBQcmFjdGljZQ0KDQpXZSBmb2N1cyBvbiBvbmUgdGltZSBzZXJpZXM6ICoqQnVzaW5lc3MgdHJpcHMgaW4gU3lkbmV5KiouDQoNCmBgYHtyIHN5ZG5leS1kZWNvbXBvc2l0aW9uLCBmaWcuaGVpZ2h0PTh9DQojIEZpbHRlcjogQnVzaW5lc3MgdHJpcHMgaW4gU3lkbmV5DQpzeWRuZXlfYnVzaW5lc3MgPC0gdG91cmlzbSAlPiUNCiAgZmlsdGVyKFJlZ2lvbiA9PSAiU3lkbmV5IiwgUHVycG9zZSA9PSAiQnVzaW5lc3MiKSAlPiUNCiAgYXNfdHNpYmJsZShpbmRleCA9IFF1YXJ0ZXIpDQoNCiMgQ29udmVydCB0byB0cyBvYmplY3QgZm9yIGRlY29tcG9zaXRpb24NCnRzX3N5ZG5leSA8LSB0cyhzeWRuZXlfYnVzaW5lc3MkVHJpcHMsIGZyZXF1ZW5jeSA9IDQpDQoNCiMgUGVyZm9ybSBTVEwgZGVjb21wb3NpdGlvbg0KZGVjb21wX3N5ZG5leSA8LSBzdGwodHNfc3lkbmV5LCBzLndpbmRvdyA9ICJwZXJpb2RpYyIpDQoNCiMgUGxvdCBkZWNvbXBvc2l0aW9uDQpwYXIobWZyb3cgPSBjKDQsIDEpLCBtYXIgPSBjKDMsIDQsIDIsIDIpKQ0KcGxvdChkZWNvbXBfc3lkbmV5LCBtYWluID0gIlNUTCBEZWNvbXBvc2l0aW9uOiBCdXNpbmVzcyBUcmlwcyBpbiBTeWRuZXkiKQ0KDQojIEV4dHJhY3QgY29tcG9uZW50cw0KY29tcG9uZW50c19kZiA8LSB0aWJibGUoDQogIFF1YXJ0ZXIgPSBzeWRuZXlfYnVzaW5lc3MkUXVhcnRlciwNCiAgT2JzZXJ2ZWQgPSBzeWRuZXlfYnVzaW5lc3MkVHJpcHMsDQogIFRyZW5kID0gYXMubnVtZXJpYyhkZWNvbXBfc3lkbmV5JHRpbWUuc2VyaWVzWywgInRyZW5kIl0pLA0KICBTZWFzb25hbCA9IGFzLm51bWVyaWMoZGVjb21wX3N5ZG5leSR0aW1lLnNlcmllc1ssICJzZWFzb25hbCJdKSwNCiAgUmVtYWluZGVyID0gYXMubnVtZXJpYyhkZWNvbXBfc3lkbmV5JHRpbWUuc2VyaWVzWywgInJlbWFpbmRlciJdKQ0KKQ0KDQojIERlY29tcG9zaXRpb24gc3RhdGlzdGljcw0KY29tcG9uZW50c19kZiAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIE9ic2VydmVkX01lYW4gPSBtZWFuKE9ic2VydmVkKSwNCiAgICBUcmVuZF9NZWFuID0gbWVhbihUcmVuZCksDQogICAgU2Vhc29uYWxfTWVhbiA9IG1lYW4oU2Vhc29uYWwpLA0KICAgIFNlYXNvbmFsX0FtcCA9IG1heChTZWFzb25hbCkgLSBtaW4oU2Vhc29uYWwpLA0KICAgIFJlbWFpbmRlcl9TRCA9IHNkKFJlbWFpbmRlcikNCiAgKQ0KDQojIFZpc3VhbGl6ZSBjb21wb25lbnRzDQpjb21wb25lbnRzX2xvbmcgPC0gY29tcG9uZW50c19kZiAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtUXVhcnRlciwgbmFtZXNfdG8gPSAiQ29tcG9uZW50IiwgdmFsdWVzX3RvID0gIlZhbHVlIikNCg0KcF9kZWNvbXAgPC0gZ2dwbG90KGNvbXBvbmVudHNfbG9uZywgYWVzKHggPSBRdWFydGVyLCB5ID0gVmFsdWUpKSArDQogIGdlb21fbGluZShjb2xvciA9ICJibHVlIiwgbGluZXdpZHRoID0gMC44KSArDQogIGZhY2V0X3dyYXAofkNvbXBvbmVudCwgc2NhbGVzID0gImZyZWVfeSIsIG5jb2wgPSAxKSArDQogIGxhYnModGl0bGUgPSAiQnVzaW5lc3MgVHJpcHMgaW4gU3lkbmV5IC0gQ29tcG9uZW50IEFuYWx5c2lzIiwgeCA9ICJRdWFydGVyIiwgeSA9ICJUcmlwcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCnBfZGVjb21wDQpgYGANCg0KIyMjIDYuNiBJbnRlcmFjdGl2ZSBWaXN1YWxpemF0aW9uDQoNCldlIGNyZWF0ZSBhbiBpbnRlcmFjdGl2ZSB0aW1lIHNlcmllcyBwbG90IHVzaW5nICoqcGxvdGx5KiouDQoNCmBgYHtyIGludGVyYWN0aXZlLXBsb3QsIHJlc3VsdHM9J2FzaXMnfQ0KIyBQcmVwYXJlIGRhdGEgZm9yIGludGVyYWN0aXZlIHBsb3QgKGNvbnZlcnQgdG8gcGxhaW4gdGliYmxlIHRvIGF2b2lkIHRzaWJibGUgaW5kZXggY29uc3RyYWludHMpDQppbnRlcmFjdGl2ZV9kYXRhIDwtIHRvdXJpc20gJT4lDQogIGFzX3RpYmJsZSgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyA8LS0gZHJvcCB0c2liYmxlIHNlbWFudGljcw0KICBncm91cF9ieShRdWFydGVyLCBQdXJwb3NlKSAlPiUNCiAgc3VtbWFyaXNlKHRvdGFsX3RyaXBzID0gc3VtKFRyaXBzKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KIyBDcmVhdGUgaW50ZXJhY3RpdmUgcGxvdA0KcF9pbnRlcmFjdGl2ZSA8LSBwbG90X2x5KA0KICBpbnRlcmFjdGl2ZV9kYXRhLA0KICB4ID0gflF1YXJ0ZXIsIHkgPSB+dG90YWxfdHJpcHMsIGNvbG9yID0gflB1cnBvc2UsDQogIHR5cGUgPSAic2NhdHRlciIsIG1vZGUgPSAibGluZXMiLA0KICBob3ZlcnRlbXBsYXRlID0gcGFzdGUoDQogICAgIlF1YXJ0ZXI6ICV7eH08YnI+IiwNCiAgICAiVHJpcHM6ICV7eTosfTxicj4iLA0KICAgICJQdXJwb3NlOiAle3RleHR9PGV4dHJhPjwvZXh0cmE+Ig0KICApLA0KICB0ZXh0ID0gflB1cnBvc2UNCikgJT4lDQogIGxheW91dCgNCiAgICB0aXRsZSA9ICJBdXN0cmFsaWFuIFRvdXJpc20gYnkgUHVycG9zZSAoSW50ZXJhY3RpdmUpIiwNCiAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiUXVhcnRlciIpLA0KICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJUb3RhbCBUcmlwcyIpLA0KICAgIGhvdmVybW9kZSA9ICJ4IHVuaWZpZWQiDQogICkNCg0KcF9pbnRlcmFjdGl2ZQ0KYGBgDQoNCg0KIyMgU3VtbWFyeSBhbmQgS2V5IFRha2Vhd2F5cw0KDQpZb3UgaGF2ZSBsZWFybmVkIGhvdyB0bzoNCg0KMS4gKipDcmVhdGUgYW5kIG1hbmFnZSB0ZW1wb3JhbCBkYXRhKiogIA0KICAgLSBVc2UgYERhdGVgIGFuZCBgUE9TSVhjdGAgY2xhc3NlcyAgDQogICAtIFBhcnNlIGRhdGVzIHdpdGggKmx1YnJpZGF0ZSogKGB5bWQoKWAsIGBkbXkoKWAsIGBtZHkoKWApICANCiAgIC0gRXh0cmFjdCBkYXRlIGNvbXBvbmVudHMgKGB5ZWFyKClgLCBgbW9udGgoKWAsIGB3ZGF5KClgKSAgDQogICAtIFBlcmZvcm0gZGF0ZSBhcml0aG1ldGljDQoNCjIuICoqV29yayB3aXRoIHRpbWUgc2VyaWVzIGRhdGEqKiAgDQogICAtIENyZWF0ZSByZWd1bGFyIHRpbWUgc2VxdWVuY2VzIHdpdGggYHNlcSgpYCAgDQogICAtIEhhbmRsZSByZWFsLXdvcmxkIHRpbWUgc2VyaWVzIGRhdGEgIA0KICAgLSBDb252ZXJ0IGJldHdlZW4gZGlmZmVyZW50IHRpbWUgc2VyaWVzIGZvcm1hdHMNCg0KMy4gKipQZXJmb3JtIHRpbWUgc2VyaWVzIGFnZ3JlZ2F0aW9uKiogIA0KICAgLSBBZ2dyZWdhdGUgZGF0YSBieSBkaWZmZXJlbnQgdGltZSBwZXJpb2RzICANCiAgIC0gVXNlIGBncm91cF9ieSgpYCBhbmQgYHN1bW1hcmlzZSgpYCBmb3IgYWdncmVnYXRpb24gIA0KICAgLSBWaXN1YWxpemUgYWdncmVnYXRlZCB0aW1lIHNlcmllcw0KDQo0LiAqKkRlY29tcG9zZSB0aW1lIHNlcmllcyoqICANCiAgIC0gVW5kZXJzdGFuZCB0cmVuZCwgc2Vhc29uYWwsIGFuZCByYW5kb20gY29tcG9uZW50cyAgDQogICAtIFBlcmZvcm0gY2xhc3NpY2FsIGRlY29tcG9zaXRpb24gd2l0aCBgZGVjb21wb3NlKClgICANCiAgIC0gVXNlIFNUTCBkZWNvbXBvc2l0aW9uIGZvciByb2J1c3QgYW5hbHlzaXMgIA0KICAgLSBJbnRlcnByZXQgZGVjb21wb3NpdGlvbiByZXN1bHRzDQoNCjUuICoqQXBwbHkgdG8gcmVhbC13b3JsZCBkYXRhKiogIA0KICAgLSBMb2FkIGFuZCBleHBsb3JlIHJlYWwgdGltZSBzZXJpZXMgZGF0YXNldHMgIA0KICAgLSBDcmVhdGUgbWVhbmluZ2Z1bCB2aXN1YWxpemF0aW9ucyAgDQogICAtIFBlcmZvcm0gYW5hbHlzaXMgb24gYWN0dWFsIGRhdGENCg0KKipLZXkgZnVuY3Rpb25zIHRvIHJlbWVtYmVyOioqICANCi0gKmx1YnJpZGF0ZSo6IGB5bWQoKWAsIGBmbG9vcl9kYXRlKClgLCBgeWVhcigpYCwgYG1vbnRoKClgLCBgd2RheSgpYCAgDQotICpkcGx5cio6IGBncm91cF9ieSgpYCwgYHN1bW1hcmlzZSgpYCwgYG11dGF0ZSgpYCAgDQotICpnZ3Bsb3QyKjogYGdncGxvdCgpYCwgYGdlb21fbGluZSgpYCwgYGdlb21fY29sKClgICANCi0gKmZvcmVjYXN0KjogYGRlY29tcG9zZSgpYCwgYHN0bCgpYCAgDQotICp0c2liYmxlKjogYGFzX3RzaWJibGUoKWAgZm9yIHRpZHkgdGltZSBzZXJpZXMNCg0KIyMgQWRkaXRpb25hbCBSZXNvdXJjZXMNCg0KQmVsb3cgYXJlIHNvbWUgaGVscGZ1bCBvbmxpbmUgcmVmZXJlbmNlcyBmb3IgZGVlcGVuaW5nIHlvdXIgdW5kZXJzdGFuZGluZyBvZiB0aW1lIHNlcmllcyBhbmFseXNpcywgUiB0aW1lIHNlcmllcyBwYWNrYWdlcywgdmlzdWFsaXphdGlvbiB0b29scywgYW5kIGZvcmVjYXN0aW5nIHRlY2huaXF1ZXMuDQoNCiMjIyBUaW1lIFNlcmllcyBDb25jZXB0cyAmIFR1dG9yaWFscw0KLSBIeW5kbWFuICYgQXRoYW5hc29wb3Vsb3Mg4oCUICpGb3JlY2FzdGluZzogUHJpbmNpcGxlcyBhbmQgUHJhY3RpY2UgKEZyZWUgT25saW5lIEJvb2spKiAgDQogIGh0dHBzOi8vb3RleHRzLmNvbS9mcHAzLw0KLSBEdWtlIFVuaXZlcnNpdHkg4oCUICpUaW1lIFNlcmllcyBBbmFseXNpcyBUdXRvcmlhbCogIA0KICBodHRwczovL3Blb3BsZS5kdWtlLmVkdS9+cm5hdS80MTFob21lLmh0bQ0KLSBQZW5uIFN0YXRlIOKAlCAqU1RBVCA1MTA6IEFwcGxpZWQgVGltZSBTZXJpZXMgQW5hbHlzaXMqICANCiAgaHR0cHM6Ly9vbmxpbmUuc3RhdC5wc3UuZWR1L3N0YXQ1MTAvDQoNCiMjIyBSIFRpbWUgU2VyaWVzIFBhY2thZ2VzICYgRG9jdW1lbnRhdGlvbg0KLSAqKnRzaWJibGUqKiBwYWNrYWdlIGRvY3VtZW50YXRpb24gIA0KICBodHRwczovL3RzaWJibGUudGlkeXZlcnRzLm9yZy8NCi0gKipmZWFzdHMqKiBmb3IgZGVjb21wb3NpdGlvbiAmIGZlYXR1cmUgZXh0cmFjdGlvbiAgDQogIGh0dHBzOi8vZmVhc3RzLnRpZHl2ZXJ0cy5vcmcvDQotICoqZmFibGUqKiBmb3JlY2FzdGluZyBmcmFtZXdvcmsgIA0KICBodHRwczovL2ZhYmxlLnRpZHl2ZXJ0cy5vcmcvDQotICoqZm9yZWNhc3QqKiBwYWNrYWdlIChiYXNlIGZvcmVjYXN0aW5nIG1ldGhvZHMpICANCiAgaHR0cHM6Ly9wa2cucm9iamh5bmRtYW4uY29tL2ZvcmVjYXN0Lw0KLSAqKmx1YnJpZGF0ZSoqIGZvciB3b3JraW5nIHdpdGggZGF0ZXMgIA0KICBodHRwczovL2x1YnJpZGF0ZS50aWR5dmVyc2Uub3JnLw0KDQojIyMgVmlzdWFsaXphdGlvbiAmIFBsb3R0aW5nDQotIGdncGxvdDIgb2ZmaWNpYWwgZG9jdW1lbnRhdGlvbiAgDQogIGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnLw0KLSBwbG90bHkgZm9yIGludGVyYWN0aXZlIGNoYXJ0cyAgDQogIGh0dHBzOi8vcGxvdGx5LmNvbS9yLw0KDQojIyMgT3BlbiBEYXRhIFNvdXJjZXMgZm9yIFByYWN0aWNlDQotIEF1c3RyYWxpYW4gR292ZXJubWVudCBUb3VyaXNtIERhdGEgKEFCUykgIA0KICBodHRwczovL3d3dy5hYnMuZ292LmF1L3N0YXRpc3RpY3MvaW5kdXN0cnkvdG91cmlzbS1hbmQtdHJhbnNwb3J0DQotIEV1cm9wZWFuIENlbnRyZSBmb3IgTWVkaXVt4oCRUmFuZ2UgV2VhdGhlciBGb3JlY2FzdHMgKEVDTVdGKSAgDQogIGh0dHBzOi8vd3d3LmVjbXdmLmludC9lbi9mb3JlY2FzdHMvZGF0YXNldHMNCi0gV29ybGQgQmFuayBPcGVuIERhdGEgIA0KICBodHRwczovL2RhdGEud29ybGRiYW5rLm9yZy8NCi0gTk9BQSBDbGltYXRlIERhdGEgIA0KICBodHRwczovL3d3dy5uY2RjLm5vYWEuZ292L2RhdGEtYWNjZXNzDQoNCiMjIyBBZGRpdGlvbmFsIExlYXJuaW5nDQotIFJTdHVkaW8gQ2hlYXRzaGVldHMgKGluY2x1ZGluZyBUaW1lIFNlcmllcywgRGF0YSBXcmFuZ2xpbmcsIGdncGxvdDIpICANCiAgaHR0cHM6Ly9wb3NpdC5jby9yZXNvdXJjZXMvY2hlYXRzaGVldHMvDQotIEthZ2dsZSBUaW1lIFNlcmllcyBEYXRhc2V0cyAgDQogIGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHM/c2VhcmNoPXRpbWUrc2VyaWVzDQoNCg0KLS0tDQoqKlRoaXMgbWF0ZXJpYWwgaXMgcGFydCBvZiB0aGUgdHJhaW5pbmcgcHJvZ3JhbSBieSBUaGUgTmF0aW9uYWwgQ2VudHJlIGZvciBSZXNlYXJjaCBNZXRob2RzIMKpIFtOQ1JNXShodHRwczovL3d3dy5uY3JtLmFjLnVrL2Fib3V0LykgYXV0aG9yZWQgYnkgW0Ry4oCvU29tbmF0aOKAr0NoYXVkaHVyaV0oaHR0cHM6Ly93d3cuc291dGhhbXB0b24uYWMudWsvcGVvcGxlLzY1Y3RxOC9kb2N0b3Itc29tbmF0aC1jaGF1ZGh1cmkpIChVbml2ZXJzaXR5IG9mIFNvdXRoYW1wdG9uKS4gQ29udGVudCBpcyB1bmRlciBhIEND4oCvQlnigJFzdHlsZSBwZXJtaXNzaXZlIGxpY2Vuc2UgYW5kIGNhbiBiZSBmcmVlbHkgdXNlZCBmb3IgZWR1Y2F0aW9uYWwgcHVycG9zZXMgd2l0aCBwcm9wZXIgYXR0cmlidXRpb24uKioNCg==